// Copyright (c) 2023 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#include <avr/io.h>
#include <avr/sfr_defs.h>

#include <Arduino.h>

#include "ioxx_common.hpp"


/**
 * @brief 特定用于ATmega128 芯片的硬件功能
 *
 * 开发板的LED 连接在16 号引脚（PG3），编译选项里要设置LED_BUILTIN=16
 *
 */
namespace mega128_specific {

    /**
     * @brief 启用JTAG 调试端口
     *
     * 设置JTAGEN 熔丝位，再启用JTAG 端口，将会占用A4 ~ A7 引脚。
     *
     */
    inline void enable_jtag() {
        MCUCR &= ~(1 << JTD);
        MCUCR &= ~(1 << JTD);
    }


    inline void disable_jtag() {
        MCUCR |= (1 << JTD);
        MCUCR |= (1 << JTD);
    }


    /**
     * @brief 复位Timer 3 2 1 共享的预分频器
     *
     * Timer 3 2 1 共享同一个预分频器，预分频器同时输出所有分频信号，三个定时器可以分别选择不同的分频比。
     * 但对预分频器的操作会同时作用于三个定时器。
     *
     * 预分频器复位后，可以保证下一次定时器计数更新一定发生在一个计数周期后。
     * 预分频器与定时器是独立工作的，即使定时器不启动，预分频器也一直在运行。
     * 所以如果定时器启动时，预分频器刚好已经计数到一个分频比附近，定时器就会在启动后很快更新一次。
     *
     * 复位标志会被硬件自动清除，所以分频器会在复位后自动继续计数。
     *
     */
    inline void reset_prescaler_for_timer_321() {
        SFIOR |= _BV(PSR321);
    }


    /**
     * @brief 让所有定时器进入“同步模式”，停止分频器计数
     *
     * 所有定时器的分频器都保持复位状态，所以定时器也都停止更新，用于同步的修改定时器计数值。
     *
     */
    inline void halt_all_timer_prescaler() {
        SFIOR |= _BV(TSM);
    }


    /**
     * @brief 重启并复位所有定时器的预分频器
     *
     */
    inline void restart_all_timer_prescaler() {
        SFIOR &= ~_BV(TSM);
    }


    /**
     * @brief Timer 3 2 1 的时钟源和分频比配置
     *
     * 由于Timer2 没有单独的比较输出，而是和Timer1 共用一个引脚，所以默认不使用Timer2 产生PWM。
     *
     * 对于16 位定时器Timer1 和Timer3
     * PWM 频率是：f_fast_pwm = f_cpu / ( div * ( 1 + ICRn) ) (mode 14);
     * 占空比是：  (OCRnX + 1) / (ICRn + 1)
     *
     * ICRn 是16 位计数器的输入捕获寄存器，此处用作定时器的计数上界，类似STM32 定时器的ARR 寄存器，一个定时器只有一个。
     * OCRnX 是16 位计数器的输出比较寄存器，一个定时器配三个，所以有三个输出通道。输入捕获和输出比较寄存器不复用，
     * 这是与STM32 的设计的差异。
     *
     * F_CPU 设为12MHz，则：
     *
     * 如果用来控制舵机，发送50Hz 信号，分频设为8，计数周期30000，ICR 为29999，f_fast_pwm = 12MHz / (8 * 30000) = 50Hz。
     * 此时占空比最低1 / 300 %，最大100%。
     * 占空比每级对应2 / 3 微秒，舵机控制信号范围500 ~ 2500us，分为3000 级，
     * 对于180° 舵机，每级对应0.06°
     *
     * 如果使用了MsTimer2 库，它会自动把timer2 分频比设置为64，如果timer1 和timer3 与之一致，此时要生成50Hz PWM，计数周期为3750，ICR 为3749。
     * 占空比每级对应5 + 2 / 3 微妙，舵机信号分为375 级，
     * 对于180° 舵机，每级对应0.48°
     *
     * 对于8 位定时器Timer2
     * CTC 模式中断频率：f_cpu / (div * (1 + OCR2) )
     *
     * 如果分频比为1024，当OCR2 == 119 时，中断频率是100Hz，周期10ms。
     *
     */
    enum class timer_321_source : uint8_t {
        none = 0,

        // 以下为定时器使用的分频比，基于F_CPU
        div_1 = _BV(CS10),
        div_8 = _BV(CS11),
        div_64 = _BV(CS10) | _BV(CS11),
        div_256 = _BV(CS12),
        div_1024 = _BV(CS10) | _BV(CS12),

        // 或者用外部时钟源，没有分频
        external_fall = _BV(CS11) | _BV(CS12),
        external_rise = _BV(CS10) | _BV(CS11) | _BV(CS12),

        clear_mask = external_rise,
    };


    inline void set_timer3_clk_source(timer_321_source s) {
        TCCR3B = (TCCR3B & (~_IOXX_ENUM_TO_UNDERLYING(timer_321_source::clear_mask)))
               | _IOXX_ENUM_TO_UNDERLYING(s);
    }


    inline void set_timer2_clk_source(timer_321_source s) {
        TCCR2 = (TCCR2 & (~_IOXX_ENUM_TO_UNDERLYING(timer_321_source::clear_mask)))
              | _IOXX_ENUM_TO_UNDERLYING(s);
    }


    inline void set_timer1_clk_source(timer_321_source s) {
        TCCR1B = (TCCR1B & (~_IOXX_ENUM_TO_UNDERLYING(timer_321_source::clear_mask)))
               | _IOXX_ENUM_TO_UNDERLYING(s);
    }


    // ================ Timer2 定时设置  =====================

    // Arduino 上的FreeRTOS 库https://github.com/feilipu/avrfreertos 默认采用15ms 周期的WDT 中断作为系统Tick，
    // Mega128 的WDT 没有中断功能，不能用作时钟源，所以可以考虑用Timer2 实现。
    // Arduino 内部毫秒计时基于Timer0，PWM 引脚连接到Timer0、1、3，没用到2，所以用Timer2 提供Tick 副作用比较小。
    // 默认令Timer2 工作在CTC 模式，用最大的分频比例1024，中断周期约16ms，可以适应F_CPU 在12MHz 或16MHz 的情况。


    /**
     * @brief 关闭定时器2 的比较和溢出中断，分频器调整为不运行状态，波形设置清空
     *
     */
    inline void reset_timer2() {
        TCCR2 = 0;
        TIFR |= (_BV(OCF2) | _BV(TOV2));      // 清零比较和溢出中断标志
        TIMSK &= ~(_BV(OCIE2) | _BV(TOIE2));  // 清零定时器2 的比较和溢出中断使能
        TCNT2 = 0;                             // 清零计数器
    }


    /**
     * @brief 使能Timer2 比较中断
     *
     */
    inline void enable_timer2_compare_irq() {
        TIFR |= _BV(OCF2);
        TIMSK |= _BV(OCIE2);
    }


    /**
     * @brief 使能Timer2 溢出中断
     *
     */
    inline void enable_timer2_overflow_irq() {
        TIFR |= _BV(TOV2);
        TIMSK |= _BV(TOIE2);
    }


    /**
     * @brief 配置timer2 为CTC 模式，OC2F 中断周期约16 毫秒
     *
     * 还需要调用enable_timer2_compare_irq 才能启用16ms 周期的CTC 中断
     *
     */
    inline void start_timer2_ctc_period_16ms() {
        reset_timer2();

#ifndef F_CPU
    #error "F_CPU must be defined."
#endif

#if F_CPU == (12'000'000L)  // 12MHz

        OCR2 = 186;  // 计数TOP 值。

#elif F_CPU == (16'000'000L)  // 16MHz

        OCR2 = 249;

#else

    #error "Unsupported F_CPU value."

#endif

        uint8_t tccr_value = _BV(WGM21);        // 设置为CTC 模式
        tccr_value |= (_BV(CS22) | _BV(CS20));  // 分频比例1024
        TCCR2 = tccr_value;
    }


    // ================= 16 BIT 定时器功能 ====================


    enum class timer_16bit {
        n1,
        n3
    };


    /**
     * @brief 初始化16 位定时器timer1 和3 为PWM 模式。
     *
     * 默认值：64分频，FastPWM 模式，以ICRn 为计数上界（mode 14）。
     *
     * PWM 频率是：f_fast_pwm = f_cpu / ( div * ( 1 + ICRn) ) (mode 14);
     * 占空比是：  (OCRnX + 1) / (ICRn + 1)
     *
     * 此时要生成50Hz PWM，计数周期为3750，ICR 为3749。
     * 占空比每级对应5 + 2 / 3 微妙，舵机信号分为375 级，
     * 对于180° 舵机，每级对应0.48°
     *
     */
    class Pwm16bitInit {
        uint8_t _tmp_tccr_n_a = 0 | _BV(WGM11);
        uint8_t _tmp_tccr_n_b = 0 | _BV(WGM12) | _BV(WGM13) | _IOXX_ENUM_TO_UNDERLYING(timer_321_source::div_64);
        uint16_t _period_value = 0;

       public:
        /**
         * @brief 设为FastPWM 模式，以ICRn 为计数上界
         *
         */
        Pwm16bitInit& set_as_fast_pwm_mode_with_icr_top() {
            _tmp_tccr_n_a &= ~_BV(WGM10);
            _tmp_tccr_n_a |= _BV(WGM11);
            _tmp_tccr_n_b |= _BV(WGM12) | _BV(WGM13);
            return *this;
        }


        Pwm16bitInit& fast_mode_period(uint16_t period_val) {
            _period_value = period_val - 1;
            return *this;
        }


        Pwm16bitInit& icr(uint16_t icr_val) {
            _period_value = icr_val;
            return *this;
        }


        Pwm16bitInit& clk_source(timer_321_source t) {
            _tmp_tccr_n_b &= (~_IOXX_ENUM_TO_UNDERLYING(timer_321_source::clear_mask));
            _tmp_tccr_n_b |= _IOXX_ENUM_TO_UNDERLYING(t);
            return *this;
        }


        /**
         * @brief 启用输出通道A
         *
         * 输出模式设为non-inverting 模式，一个PWM 周期中，
         * 先输出高电平，再低电平
         *
         * @return Pwm16bitInit&
         */
        Pwm16bitInit& enable_out_a() {
            _tmp_tccr_n_a |= _BV(COM1A1);
            _tmp_tccr_n_a &= ~_BV(COM1A0);
            return *this;
        }


        Pwm16bitInit& disable_out_a() {
            _tmp_tccr_n_a &= ~(_BV(COM1A0) | _BV(COM1A1));
            return *this;
        }


        Pwm16bitInit& enable_out_b() {
            _tmp_tccr_n_a |= _BV(COM1B1);
            _tmp_tccr_n_a &= ~_BV(COM1B0);
            return *this;
        }


        Pwm16bitInit& disable_out_b() {
            _tmp_tccr_n_a &= ~(_BV(COM1B0) | _BV(COM1B1));
            return *this;
        }


        Pwm16bitInit& enable_out_c() {
            _tmp_tccr_n_a |= _BV(COM1C1);
            _tmp_tccr_n_a &= ~_BV(COM1C0);
            return *this;
        }


        Pwm16bitInit& disable_out_c() {
            _tmp_tccr_n_a &= ~(_BV(COM1C0) | _BV(COM1C1));
            return *this;
        }


        /**
         * @brief 初始化定时器为指定的模式，选中的定时器之前的寄存器值将被完全覆盖
         *
         * 如果时钟源没有选择none，调用init 后，定时器会立即开始计数。
         *
         * @param t
         * @return Pwm16bitInit&
         */
        Pwm16bitInit& init(timer_16bit t) {
            switch (t) {
                case timer_16bit::n1:
                    TCCR1A = _tmp_tccr_n_a;
                    TCCR1B = _tmp_tccr_n_b;
                    ICR1 = _period_value;
                    break;

                case timer_16bit::n3:
                    TCCR3A = _tmp_tccr_n_a;
                    TCCR3B = _tmp_tccr_n_b;
                    ICR3 = _period_value;
                    break;
            }

            return *this;
        }
    };


    /**
     * @brief 直接设置输出比较寄存器的值
     *
     * @param t
     * @param val
     */
    inline void set_compare_a(timer_16bit t, uint16_t val) {
        switch (t) {
            case timer_16bit::n1:
                OCR1A = val;
                break;

            case timer_16bit::n3:
                OCR3A = val;
                break;
        }
    }


    inline void set_compare_b(timer_16bit t, uint16_t val) {
        switch (t) {
            case timer_16bit::n1:
                OCR1B = val;
                break;

            case timer_16bit::n3:
                OCR3B = val;
                break;
        }
    }


    inline void set_compare_c(timer_16bit t, uint16_t val) {
        switch (t) {
            case timer_16bit::n1:
                OCR1C = val;
                break;

            case timer_16bit::n3:
                OCR3C = val;
                break;
        }
    }

    /**
     * @brief 占空比为val / period
     *
     */
    inline void set_fast_duty_a(timer_16bit t, uint16_t val) {
        set_compare_a(t, val - 1);
    }


    inline void set_fast_duty_b(timer_16bit t, uint16_t val) {
        set_compare_b(t, val - 1);
    }


    inline void set_fast_duty_c(timer_16bit t, uint16_t val) {
        set_compare_c(t, val - 1);
    }


    /**
     * @brief 断开所有通道的PWM 输出，清零定时器的寄存器值
     *
     */
    inline void halt_timer_and_pwm(timer_16bit t) {
        switch (t) {
            case timer_16bit::n1:
                TCCR1A = 0;
                TCCR1B = 0;
                break;

            case timer_16bit::n3:
                TCCR3A = 0;
                TCCR3B = 0;
                break;
        }
    }


    // GPIO 功能


    using PortType = decltype(PORTB);


    inline void direct_setpin(PortType port_out, uint8_t pin) {
        port_out |= _BV(pin);
    }


    inline void direct_clrpin(PortType port_out, uint8_t pin) {
        port_out &= ~_BV(pin);
    }


    inline void direct_toggle_pin(PortType port_out, uint8_t pin) {
        port_out ^= _BV(pin);
    }


    /**
     * @brief 读取引脚
     *
     * @param port 读引脚时不能PORTx ，要用PINx，比如PINB
     * @param pin
     * @return auto
     */
    inline auto direct_test_pin(PortType port_in, uint8_t pin) {
        return bit_is_set(port_in, pin);
    }


    inline void digitalToggle(uint8_t pin) {
        uint8_t bit = digitalPinToBitMask(pin);
        uint8_t port = digitalPinToPort(pin);
        volatile uint8_t* out;

        if (port == NOT_A_PIN) return;

        out = portOutputRegister(port);

        uint8_t oldSREG = SREG;
        cli();

        *out ^= bit;

        SREG = oldSREG;
    }


    // RGB

    constexpr uint8_t RGB_PIN = 31;
    constexpr uint8_t LED_PIN = 16;


}  // namespace mega128_specific
